package com.gao.b_stream.c;

import org.junit.Test;

import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.function.*;
import java.util.stream.Stream;

/**
 * @author gao
 * @time 2020/07/23 09:43:19
 * <p>
 * 1. 函数式接口是伴随着Stream的诞生而出现的
 * 2. Java8Stream是函数式编程的一种具体实现
 * 3. 函数式编程的特点是开发者无需关注怎么做，只需知道要做什么
 * 4. 函数式编程的有点： 给开发者带来了简单快速处理数据的体验
 */

public class App {
    /*
        函数式接口，简单来说就是只有一个抽象方法的接口。
        为了让函数式接口的定义变得更加的规范，java8提供了@FunctionalInterface注解修
        被@FunctionalInterface修饰的接口，必须满足以下规则：
        1. 必须有一个抽象方法
        2. 如果有一个以上的抽象方法，则除了一个抽象方法以外，其他抽象烦恼歌发必须指定默认方法体
     */
    @FunctionalInterface
    interface Foo {
        void f1();

        default void f2() {
        }
    }
    /*
    实际上并不需要@FunctionalInterface 注解，只要一个抽象方法就能实现函数式接口
    但是不加注解不影响正常运行，但是别人在修改代码的时候，就不知道这是一个函数式接口，
    可能会添加新的抽象方法，这样其他程序就会收到影响。这个跟override注解是一样的效果
     */


    /*
        Java8 util.function 包下自带了43个函数式接口，大体分为以下几类：
        Consumer 消费接口
        Function 功能接口
        Operator 操作接口
        Predicate 断言接口
        Supplier 生产接口
     */


    //  一、 Consumer 消费接口
    @Test
    public void test() {

        class A {
        }
        class B extends A {
        }
        class C extends B {
        }
        class D extends C {
        }

        Consumer<C> c = new Consumer<C>() {
            @Override
            public void accept(C c) {
            }

            // 以下方法的作用是，让多个Consumer连续操作一个输入的参数
            // 以下的Consumer<? super C> after参数中，泛型的含义是：
            // 能操作C的父类的Consumer，都能操作C
            @Override
            public Consumer<C> andThen(Consumer<? super C> after) {
                return (c) -> {
                    accept(c);
                    after.accept(c);
                };
            }
        };

        Consumer<B> c2 = null;
        Consumer<D> c3 = null;

        c.andThen(c2);
        // can't compile
        // c.andThen(c3);
    }

    @Test
    public void test2() {
        Consumer<Integer> c = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("数字：" + integer);
            }
        };
        Consumer<Integer> c2 = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("平方：" + integer * integer);
            }
        };
        Consumer<Integer> c3 = new Consumer<Integer>() {
            @Override
            public void accept(Integer integer) {
                System.out.println("立方：" + integer * integer * integer);
            }
        };
        Stream.of(1, 2, 3, 4, 5, 6)
                .forEach(c.andThen(c2).andThen(c3));
    }

    @Test
    public void test3() {
        Consumer<Integer> c = e -> System.out.println("数字：" + e);
        Consumer<Integer> c2 = e -> System.out.println("平方：" + e * e);
        Consumer<Integer> c3 = e -> System.out.println("立方：" + e * e * e);
        Stream.of(1, 2, 3, 4, 5, 6)
                .forEach(c.andThen(c2).andThen(c3));
    }

    // 二、 Function 转换接口
    @Test
    public void test4() {
        Function<String, Integer> f = new Function<String, Integer>() {
            @Override
            public Integer apply(String s) {
                System.out.println("A");
                return s.length();
            }

            /*
                compose方法是将2个Function操作结合起来，before操作完的结果，再交给当前对象操作，无论怎么操作，
                最终的结果都必须是Integer类型！
                所以compose方法返回的Funcation，返回的结果类型，必须与当前Function对象要求的返回类型一致（Integer），
                而参数考科一接收任意一个类型的参数（由V参数类型决定，这里使用了自动类型识别），该参数先交给before的apply
                处理，before.appley处理完之后，返回的结果类型必须是当前对象的apply方法要求的类型（String），这样当前
                对象才能继续处理。
             */
            @Override
            public <V> Function<V, Integer> compose(Function<? super V, ? extends String> before) {
                System.out.println("B");
                return (V v) -> apply(before.apply(v));
            }
        };

        Function<Date, String> f2 = new Function<Date, String>() {
            @Override
            public String apply(Date date) {
                return date.getYear() + "";
            }
        };

        // f3就是f和f2组合操作之后的转换函数对象，且是先执行把传递给f3的参数，交给f2的操作，再把f2操作的结果交给f操作
        // 最终返回f操作的结果
        Function<Date, Integer> f3 = f.compose(f2);

        Stream.of(new Date(), new Date(), new Date())
                .map(f3)
                .forEach(e -> System.out.println(e));
    }

    @Test
    public void test5() {
        Function<String, Integer> f = (s) -> s.length();
        Function<Date, String> f2 = (d) -> d.getTime() + "";
        // f3会把自己apply方法所接受到参数，交给f2.apply执行，再把f2.apply返回的结果交给f.apply执行
        // 最终返回f.apply操作的结果
        Function<Date, Integer> f3 = f.compose(f2);
    }

    // Consumer和Function的区别就是，Consumer不会传递结果，毕竟Consumer的accept方法没有返回值。

    @Test
    public void test6() {
        Function<Integer, Integer> square = n -> n * n;
        List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
        // 平方之后，再平方, 注意使用的是Function的andThen方法
        list.stream().map(square.andThen(square)).forEach(System.out::println);

        System.out.println("-----");
        // 先减1，再平方，注意使用的是Function的compose方法
        list.stream().map(square.compose(e -> e - 1)).forEach(e -> System.out.println(e));

        System.out.println("-----");

        // 先平方，再除以2，再平方
        list.stream().map(square.andThen(square.compose(e -> e / 2))).forEach(e -> System.out.println(e));
    }

    // 三、Operator
    @Test
    public void test7() {
        BinaryOperator<Integer> b = new BinaryOperator<Integer>() {
            @Override
            public Integer apply(Integer a, Integer b) {
                System.out.println(a + " " + b);
                return a + b;
            }
        };

        Integer sum = Stream.of(2, 4, 6, 8, 10)
                .reduce(0,b);

        System.out.println("sum = " + sum);
    }

    @Test
    public void test8() {
        Integer sum = Stream.of(2, 4, 6, 8, 10)
                .reduce(0, (a,b) -> a + b);
    }

    // 四、Predicate
    @Test
    public void test9() {
        Predicate<String> p = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() > 5;
            }
        };

        Predicate<String> p2 = new Predicate<String>() {
            @Override
            public boolean test(String s) {
                return s.length() < 7;
            }
        };

        Predicate<String> p3 = p.and(p2);

        Stream.of("apple", "banana", "cucumber", "durian", "eden")
            .filter(p3)
            .forEach(System.out::println);
    }
    
    @Test
    public void test10() {
        Stream.of("apple", "banana", "cucumber", "durian", "eden")
                .filter(e -> e.startsWith("a") || e.startsWith("e"))
                .forEach(e -> System.out.println(e));
    }

    @Test
    public void test11() {
        // 筛选出大于0最小的两个数
        Stream.of(-9,-2,3,-4,11,42,10,-7,10,23)
                .filter(e -> e > 0)
                .sorted()
                .limit(2)
                .forEach(e -> System.out.println(e));

        System.out.println("-----");

        // 筛选出小于0最大的两个数
        Stream.of(-9,-2,3,-4,11,42,10,-7,10,23)
                .filter(e -> e < 0)
                .sorted((a,b) -> new Integer(b).compareTo(a))
                .limit(2)
                .forEach(e -> System.out.println(e));
    }

    // 五、Supplier
    // 这里一直生产随机数字
    @Test
    public void test12() {
        Supplier<Integer> s = new Supplier<Integer>() {
            Random r = new Random(47);
            @Override
            public Integer get() {
                return r.nextInt();
            }
        };

        Stream.generate(s).forEach(e -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException interruptedException) {
                interruptedException.printStackTrace();
            }
            System.out.println(e);
        });
    }

    // 为了能停下来，可以使用limit
    @Test
    public void test13() {
        Stream.generate(() -> (int)(Math.random() * 1000))
                .limit(5)
                .forEach(e -> System.out.println(e));
    }
}